using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using EnvDTE;
using Microsoft.Build.BuildEngine;
using Microsoft.VisualStudio.Project;
using Microsoft.WowAddonStudio.Lua.LanguageService.Tests.WowCommonTestLibrary.Mocks;
using Microsoft.WowAddonStudio.Project;
using Microsoft.WowAddonStudio.Project.Automation;
using Microsoft.WowAddonStudio.ProjectRefactor;
using NUnit.Framework;
using WowProjectPackage.Tests.Common;
using Project=Microsoft.Build.BuildEngine.Project;

namespace WowProjectPackage.Tests
{
	/// <summary>
	/// 
	/// </summary>
	[TestFixture]
	public class WowFileNodeTest : GeneralDTETestBase
	{
		protected const string Frame_Xml_Name = "Frame.xml";
		protected const string Frame_Xml_New_Name = "Frame2.xml";
		protected const string Lua_File_Name = "Frame2.lua";
		protected const string Toc_File_Name = "AddOn.toc";

		protected ProjectNode projectNode;
		protected static SimpleTestServiceLocator locator;
		protected readonly string currentDir = String.Concat(Environment.CurrentDirectory, @"\Resources");

		#region SetUp

		/// <summary>
		/// Sets up.
		/// </summary>
		[SetUp]
		public override void SetUp()
		{
			base.SetUp();

			if (!File.Exists(Path.Combine(currentDir, Frame_Xml_Name))
			    && File.Exists(Path.Combine(currentDir, Frame_Xml_New_Name)))
				File.Move(Path.Combine(currentDir, Frame_Xml_New_Name), Path.Combine(currentDir, Frame_Xml_Name));
			if (File.Exists(Path.Combine(currentDir, Frame_Xml_New_Name)))
				File.Delete(Path.Combine(currentDir, Frame_Xml_New_Name));

			locator = new SimpleTestServiceLocator();
			projectNode = new MockProjectNode(locator);
			SimpleTestServiceLocator.Register(projectNode);

			InitializeMockObjects(GetSource(Path.Combine(currentDir, Frame_Xml_Name)));

			var props = ((MockProperties) SimpleTestServiceLocator.Get<ProjectItem>().Properties);
			if (!props.ContainsKey("FullPath"))
				props.Add("FullPath", CreateMockProperty(props, "FullPath", Path.Combine(currentDir, Frame_Xml_New_Name)));

			var docs = new MockDocuments(SimpleTestServiceLocator.Get<DTE>(),
				((MockProjectItem) SimpleTestServiceLocator.Get<ProjectItem>()).Object.ToString(),
				new[] {SimpleTestServiceLocator.Get<ProjectItem>().Document});

			((MockDTE) SimpleTestServiceLocator.Get<DTE>()).SetMockDocuments(docs);
		}

		#endregion

		#region TearDown

		/// <summary>
		/// Tears down.
		/// </summary>
		[TearDown]
		public override void TearDown()
		{
			base.TearDown();
		}

		#endregion

		#region Tests

		/// <summary>
		/// Test_s the refactor reference lua code file with document.
		/// </summary>
		[Test(Description = "")]
		public void Test_RefactorReferenceLuaCodeFileWithDocument()
		{
			SetProjectNodeProperties(projectNode);

			WowFileNode fileNode = CreateWowXmlNode();
			WowFileNode luaFileNode = CreateLuaWowFileNode();

			projectNode.FirstChild = fileNode;
			fileNode.FirstChild = luaFileNode;
			fileNode.Parent = projectNode;
			fileNode.VirtualNodeName = Path.Combine(currentDir, Frame_Xml_New_Name);
			luaFileNode.VirtualNodeName = Path.Combine(currentDir, Lua_File_Name);

			SimpleTestServiceLocator.Register(fileNode as WowXmlFileNode);
			SimpleTestServiceLocator.Register(fileNode.FirstChild as WowLuaFileNode);
			//Adds spare item
			SimpleTestServiceLocator.Register(InitWowXmlNode(fileNode.ID));

			MethodInfo methodInfo = GetMethodInfo("SetEditLabel", typeof (WowFileNode));

			var ret = methodInfo.Invoke(fileNode, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null,
			                            new object[] {Frame_Xml_New_Name, Frame_Xml_Name}, null);
			Console.WriteLine(ret);
			Assert.AreEqual(_VSConstants.S_OK, ret);
		}

		[Test(Description = "")]
		public void Test_TocFileNodeDefault()
		{
			SetProjectNodeProperties(projectNode);
			WowTocFileNode fileNode = CreateWowTocFileNode();
			Assert.AreEqual((int)ProjectNode.ImageName.SettingsFile, fileNode.ImageIndex);
		}

		[Test(Description = "")]
		public void Test_XmlFileNodeDefault()
		{
			SetProjectNodeProperties(projectNode);
			WowFileNode fileNode = CreateWowXmlNode();
			Assert.AreEqual((int)ProjectNode.ImageName.XMLFile, fileNode.ImageIndex);
		}

		[Test(Description = "")]
		public void Test_LuaFileNodeDefault()
		{
			SetProjectNodeProperties(projectNode);
			WowFileNode fileNode = CreateLuaWowFileNode();
			Assert.AreEqual((int)ProjectNode.ImageName.ScriptFile, fileNode.ImageIndex);
			var automationObject = fileNode.GetAutomationObject();
			Assert.IsNotNull(automationObject);
			Assert.IsInstanceOfType(typeof(OAWowFileItem), automationObject);
		}


		/// <summary>
		/// Test_s the refactor reference lua code file with file.
		/// </summary>
		[Test(Description = "")]
		public void Test_RefactorReferenceLuaCodeFileWithFile()
		{
			//Remove TextDcoument instances from DTE.
			((MockDTE) SimpleTestServiceLocator.Get<DTE>()).SetMockDocuments(new MockDocuments(
			                                                                 	SimpleTestServiceLocator.Get<DTE>(),
			                                                                 	((MockProjectItem)
			                                                                 	 SimpleTestServiceLocator.Get<ProjectItem>()).
			                                                                 		Object.ToString()));
			Test_RefactorReferenceLuaCodeFileWithDocument();

			//Check content of FrameXml file
			if (File.Exists(Path.Combine(currentDir, Frame_Xml_New_Name)))
			{
				bool result = false;
				using (Stream stream = File.OpenRead(Path.Combine(currentDir, Frame_Xml_New_Name)))
				using (var streamReader = new StreamReader(stream))
				{
					var sourceReader = new StringReader(streamReader.ReadToEnd());
					using (XmlReader reader = XmlReader.Create(sourceReader))
					{
						XElement root = XElement.Load(reader);
						var namespaceManager = new XmlNamespaceManager(reader.NameTable);
						namespaceManager.AddNamespace("ans", FrameXmlMergerFactory.BlizzardNamespace);
						foreach (var scriptElement in root.XPathSelectElements("//ans:Script", namespaceManager))
						{
							// Get the filename of the reference script file
							XAttribute fileAttribute = scriptElement.Attribute("file");
							Assert.IsNotNull(fileAttribute);
							Assert.AreEqual(Lua_File_Name, fileAttribute.Value);
							result = true;
						}
					}
				}

				Assert.IsTrue(result);
			}
			else
			{
				throw new FileNotFoundException("Test file not found!", Path.Combine(currentDir, Frame_Xml_New_Name));
			}
		}

		#endregion

		#region Protected Helpers

		/// <summary>
		/// Inits the wow XML node.
		/// </summary>
		/// <param name="Id">The id.</param>
		/// <returns></returns>
		protected virtual WowFileNode InitWowXmlNode(uint Id)
		{
			WowFileNode fileNode = CreateWowXmlNode();
			fileNode.VirtualNodeName = Path.Combine(currentDir, Frame_Xml_New_Name);
			fileNode.Parent = projectNode;
			Type type = typeof (HierarchyNode);
			PropertyInfo propInfo = type.GetProperty("ID", BindingFlags.Public | BindingFlags.Instance |
			                                               BindingFlags.GetProperty);
			propInfo.SetValue(fileNode, Id,
			                  BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
			                  null, null, null);
			return fileNode;
		}

		/// <summary>
		/// Creates the wow toc file node.
		/// </summary>
		/// <returns></returns>
		protected WowTocFileNode CreateWowTocFileNode()
		{
			ProjectElement element = CreateProjectElement(projectNode);
			SetMetadata(ProjectFileConstants.Include, Path.Combine(currentDir, Toc_File_Name), element);
			SetMetadata(ProjectFileConstants.SubType, WowSubType.TableOfContents, element);

			Type type = typeof(WowTocFileNode);
			Type[] paramArray = { typeof(ProjectNode), typeof(ProjectElement) };
			ConstructorInfo ci = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
													 null, paramArray, null);
			object[] paramsX = { projectNode, element };
			return ci.Invoke(paramsX) as WowTocFileNode;
		}

		/// <summary>
		/// Creates the wow XML node.
		/// </summary>
		/// <returns></returns>
		protected virtual WowFileNode CreateWowXmlNode()
		{
			ProjectElement element = CreateProjectElement(projectNode);
			SetMetadata(ProjectFileConstants.Include, Path.Combine(currentDir, Frame_Xml_Name), element);
			SetMetadata(ProjectFileConstants.SubType, WowSubType.Frame, element);

			Type type = typeof (WowXmlFileNode);
			Type[] paramArray = {typeof (ProjectNode), typeof (ProjectElement)};
			ConstructorInfo ci = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
			                                         null, paramArray, null);
			object[] paramsX = {projectNode, element};
			return ci.Invoke(paramsX) as WowFileNode;
		}

		/// <summary>
		/// Creates the lua wow file node.
		/// </summary>
		/// <returns></returns>
		protected virtual WowFileNode CreateLuaWowFileNode()
		{
			ProjectElement luaElement = CreateProjectElement(projectNode);
			SetMetadata(ProjectFileConstants.Include, Path.Combine(currentDir, Lua_File_Name), luaElement);
			SetMetadata(ProjectFileConstants.SubType, WowSubType.Code, luaElement);

			Type type = typeof (WowLuaFileNode);
			Type[] paramArray = {typeof (ProjectNode), typeof (ProjectElement)};
			ConstructorInfo ci = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
			                                         null, paramArray, null);
			object[] paramsX = {projectNode, luaElement};
			return ci.Invoke(paramsX) as WowLuaFileNode;
		}


		/// <summary>
		/// Sets the metadata.
		/// </summary>
		/// <param name="attributeName">Name of the attribute.</param>
		/// <param name="attributeValue">The attribute value.</param>
		/// <param name="element">The element.</param>
		protected static void SetMetadata(string attributeName, string attributeValue, ProjectElement element)
		{
			var field = element.GetType().GetField("virtualProperties", BindingFlags.NonPublic | BindingFlags.Instance |
			                                                            BindingFlags.GetField);
			var virtualProperties = (Dictionary<string, string>) field.GetValue(element) ?? new Dictionary<string, string>();
			if (virtualProperties.ContainsKey(attributeName))
				virtualProperties.Remove(attributeName);
			virtualProperties.Add(attributeName, attributeValue);
		}

		/// <summary>
		/// Sets the project node properties.
		/// </summary>
		/// <param name="projectNodeObject">The project node object.</param>
		protected virtual void SetProjectNodeProperties(ProjectNode projectNodeObject)
		{
			var project = new Project
			              	{
			              		FullFileName = Path.Combine(currentDir, Frame_Xml_Name)
			              	};
			Type type = typeof (ProjectNode);
			PropertyInfo propInfo = type.GetProperty("BuildProject",
			                                         BindingFlags.NonPublic | BindingFlags.Instance |
			                                         BindingFlags.GetProperty);
			propInfo.SetValue(projectNodeObject, project,
			                  BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
			                  null, null, null);
			propInfo = type.GetProperty("DisableQueryEdit",
			                            BindingFlags.NonPublic | BindingFlags.Instance |
			                            BindingFlags.GetProperty);
			propInfo.SetValue(projectNodeObject, true,
			                  BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
			                  null, null, null);
		}


		/// <summary>
		/// Gets the method info.
		/// </summary>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="type">The type.</param>
		/// <returns></returns>
		protected static MethodInfo GetMethodInfo(string methodName, Type type)
		{
			return type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
		}

		/// <summary>
		/// Creates the project element.
		/// </summary>
		/// <param name="projectNodeObject">The project node object.</param>
		/// <returns></returns>
		protected static ProjectElement CreateProjectElement(ProjectNode projectNodeObject)
		{
			Type type = typeof (ProjectElement);
			Type[] paramArray = {typeof (ProjectNode), typeof (BuildItem), typeof (bool)};

			ConstructorInfo ci = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, paramArray, null);
			object[] paramsX = {projectNodeObject, null, true};
			var projectElement = ci.Invoke(paramsX) as ProjectElement;
			BuildItem buildItem = AddBuildItem(projectNodeObject, "File", Frame_Xml_New_Name);
			var field = type.GetField("item", BindingFlags.NonPublic | BindingFlags.Instance |
			                                  BindingFlags.GetField);
			field.SetValue(projectElement, buildItem, BindingFlags.NonPublic | BindingFlags.Instance |
			                                          BindingFlags.SetField, null, null);

			return projectElement;
		}

		/// <summary>
		/// Adds the build item.
		/// </summary>
		/// <param name="projectNodeObject">The project node object.</param>
		/// <param name="buildItemName">Name of the build item.</param>
		/// <param name="include">The include.</param>
		/// <returns></returns>
		protected static BuildItem AddBuildItem(ProjectNode projectNodeObject, string buildItemName, string include)
		{
			Type type = typeof (ProjectNode);
			PropertyInfo propInfo = type.GetProperty("BuildProject",
			                                         BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty);
			var project =
				(Project) propInfo.GetValue(projectNodeObject, BindingFlags.NonPublic | BindingFlags.Instance, null, null, null);
			return project.AddNewItem(buildItemName, include);
		}

		#endregion
	}
}